package model;

import java.util.ArrayList;
import java.util.List;

/*
Self-explanatory. Comes with 2 methods you can use, one for integer positions and another for ElemEscenarios as positions.
Paths return are lists of ElemEscenarios, but using my class should be very simple :)
*/


public class PathFinder {

    private static Escenario escenario;
    private static ElemEscenario end;
    private static int[][] gScore;
    private static int[][] hScore;
    private static int[][] fScore;
    private static ElemEscenario[][] cameFrom;
    private static boolean[][] walls;

    private static ElemEscenario toElemEscenario(int i, int j) {
            return escenario.getPosicion(j, i).clone();
    }

    private static boolean[][] buildWalls() {
        boolean[][] auxWalls = new boolean[escenario.getX()][escenario.getY()];
        for (ArrayList<ElemEscenario> lista : escenario.getLaberinto()) {
            for (ElemEscenario elemEscenario : lista) {
                auxWalls[elemEscenario.getX()][elemEscenario.getY()] = elemEscenario.getClass().equals(Pared.class);
            }
        }
        return auxWalls;
    }

    public static ElemEscenario getProximaPosicionMasCorta(Escenario esc, ElemEscenario start, ElemEscenario finish) {
        List<ElemEscenario> generate = generate(esc, start, finish);
        if (generate.size() >= 1) return generate.get(0);
        return null;
    }


    private static List<ElemEscenario> generate(Escenario esc, ElemEscenario start, ElemEscenario finish) {
        List<ElemEscenario> openElemEscenarios = new ArrayList<ElemEscenario>();
        List<ElemEscenario> closedElemEscenarios = new ArrayList<ElemEscenario>();
        escenario = esc;
        end = finish;
        walls = buildWalls();
        gScore = new int[walls.length][walls[0].length];
        fScore = new int[walls.length][walls[0].length];
        hScore = new int[walls.length][walls[0].length];
        cameFrom = new ElemEscenario[walls.length][walls[0].length];
        openElemEscenarios.add(start);
        gScore[start.getX()][start.getY()] = 0;
        hScore[start.getX()][start.getY()] = calculateHeuristic(start);
        fScore[start.getX()][start.getY()] = hScore[start.getX()][start.getY()];

        while (openElemEscenarios.size() > 0) {
            ElemEscenario current = getLowestElemEscenarioIn(openElemEscenarios);
            if (current == null) break;
            if (current.equals(end)) {
                List<ElemEscenario> elemEscenarios = reconstructPath(current);
                elemEscenarios.remove(0);
                return elemEscenarios;
            }

            openElemEscenarios.remove(current);
            closedElemEscenarios.add(current);

            List<ElemEscenario> neighbors = getNeighborElemEscenarios(current);
            for (ElemEscenario n : neighbors) {

                if (closedElemEscenarios.contains(n)) continue;

                int tempGscore = gScore[current.getX()][current.getY()] + distanceBetween(n, current);

                boolean proceed = false;
                if (!openElemEscenarios.contains(n)) {
                    openElemEscenarios.add(n);
                    proceed = true;
                } else if (tempGscore < gScore[n.getX()][n.getY()]) proceed = true;

                if (proceed) {
                    cameFrom[n.getX()][n.getY()] = current;
                    gScore[n.getX()][n.getY()] = tempGscore;
                    hScore[n.getX()][n.getY()] = calculateHeuristic(n);
                    fScore[n.getX()][n.getY()] = gScore[n.getX()][n.getY()] + hScore[n.getX()][n.getY()];
                }
            }
        }
        return new ArrayList<ElemEscenario>();
    }


    private static List<ElemEscenario> reconstructPath(ElemEscenario n) {
        if (cameFrom[n.getX()][n.getY()] != null) {
            List<ElemEscenario> path = reconstructPath(cameFrom[n.getX()][n.getY()]);
            path.add(n);
            return path;
        } else {
            List<ElemEscenario> path = new ArrayList<ElemEscenario>();
            path.add(n);
            return path;
        }
    }

    private static List<ElemEscenario> getNeighborElemEscenarios(ElemEscenario n) {
        List<ElemEscenario> found = new ArrayList<ElemEscenario>();
        if (!walls[n.getX() + 1][n.getY()]) found.add(toElemEscenario(n.getX() + 1, n.getY()));
        if (!walls[n.getX() - 1][n.getY()]) found.add(toElemEscenario(n.getX() - 1, n.getY()));
        if (!walls[n.getX()][n.getY() + 1]) found.add(toElemEscenario(n.getX(), n.getY() + 1));
        if (!walls[n.getX()][n.getY() - 1]) found.add(toElemEscenario(n.getX(), n.getY() - 1));
        return found;
    }

    private static ElemEscenario getLowestElemEscenarioIn(List<ElemEscenario> ElemEscenarios) {
        int lowest = -1;
        ElemEscenario found = null;
        for (ElemEscenario n : ElemEscenarios) {
            int dist = cameFrom[n.getX()][n.getY()] == null ? -1 : gScore[cameFrom[n.getX()][n.getY()].getX()][cameFrom[n.getX()][n.getY()].getY()] + distanceBetween(n, cameFrom[n.getX()][n.getY()]) + calculateHeuristic(n);
            if (dist <= lowest || lowest == -1) {
                lowest = dist;
                found = n;
            }
        }
        return found;
    }

    private static int distanceBetween(ElemEscenario n1, ElemEscenario n2) {
        return (int) Math.round(10 * Math.sqrt(Math.pow(n1.getX() - n2.getX(), 2) + Math.pow(n1.getY() - n2.getY(), 2)));
    }

    private static int calculateHeuristic(ElemEscenario start) {
        return 10 * (Math.abs(start.getX() - end.getX()) + Math.abs(start.getY() - end.getY()));
    }
}